home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / ptyclean < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  37.0 KB  |  1,063 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) ptyclean.gawk 1.2 96/05/07
  4. # 94/01/18 john h. dubois iii (john@armory.com)
  5. # 94/01/20 List ttys open before each process, in case the process has 
  6. #          dissociated from the tty but stillhas it open
  7. # 94/01/26 Added i option
  8. # 94/03/09 Use gawk so - options can be given
  9. # 94/03/21 Added p option.
  10. # 94/03/27 Added ptycleanrc, and print ps line before query.
  11. # 94/04/02 Added r option.
  12. # 94/06/23 Use crash to cut down on the number of ttys fuser has to check.
  13. # 94/10/23 Print full tty numbers as progress.
  14. # 94/10/27 More error checking.
  15. # 95/06/08 Make k, q, and i be independent and exclusive of each other.
  16. # 95/06/24 Use lsof for checking master ptys because it is much faster than
  17. #          fuser (but still considerably slower than crash, so continue using
  18. #          it too).  The plan is to also use lsof for finding processes using
  19. #          slave ptys.
  20. # 95/07/15 Added t option.
  21. # 96/05/07 SCO 5.0 port.
  22.  
  23. BEGIN {
  24.     Name = "ptyclean"
  25.     Usage = "Usage: " Name " [-hikpq]"
  26.     rcFile = "/etc/default/ptyclean"
  27.     Opts(Name,Usage,"qpkithx",0,rcFile,
  28.     "QUIET,PROGRESS,KILL,INTERACTIVE,TELL",0,"r")
  29.     if ("h" in Options) {
  30.     printf \
  31. "%s: List/kill processes running on ptys whose master side is not open.\n"\
  32. "%s\n"\
  33. "The default behaviour of %s is to list processes without killing them.\n"\
  34. "The i, k, and q options can be used to kill processes.\n"\
  35. "Options:\n"\
  36. "Some of these options may be set by putting variable names in the file\n"\
  37. "%s, one per line.  The variable names are given in\n"\
  38. "parentheses in the option descriptions.\n"\
  39. "-h: Print this help.\n"\
  40. "-i: Interactive; ask whether processes should be killed after listing\n"\
  41. "    them (INTERACTIVE).\n"\
  42. "-k: Kill all found processes without asking (KILL).\n"\
  43. "-q: Quiet kill; do not list processes, just kill them (QUIET).\n"\
  44. "-t: Tell what processes are found, and then exit without killing anything.\n"\
  45. "    This is the default.  -t can be used if the default has been changed\n"\
  46. "    in the %s file (TELL).\n"\
  47. "-p: Print the number of each pty checked, as a progress report (PROGRESS).\n"\
  48. "-r: Do not read the %s file.\n",
  49.     Name,Usage,Name,rcFile,rcFile,rcFile
  50.     exit 0
  51.     }
  52.     if ((ErrStr = ExclusiveOptions("i,k,q",Options)) != "") {
  53.     printf "%s: %s\n",Name,ErrStr > "/dev/stderr"
  54.     exit 1
  55.     }
  56.     Kill = "k" in Options || (Interactive = "i" in Options) ||
  57.     (Quiet = "q" in Options)
  58.     FS = "[: ]+"
  59.     Debug = "x" in Options
  60.     if ((maxpty = ChkPtys("p" in Options,Proc2TTYs)) < 0) {
  61.     if (!Quiet)
  62.         print "No pty orphans."
  63.     exit(0)
  64.     }
  65.     for (pid in Proc2TTYs)
  66.     ProcList = ProcList " " pid
  67.     if (!Quiet || Interactive) {
  68.     if (Kill && !Interactive)
  69.         print "Killing:" ProcList
  70.     else
  71.         print "Processes:" ProcList
  72.     # Do <ps -e> rather than <ps -t ttylist> because only 20 tty names
  73.     # can be given with -t.
  74.     # Make stdin be /dev/null to work around gawk+protlib bugs.
  75.     Cmd = "exec ps -ef < /dev/null"
  76.     if (Debug)
  77.         printf "Command: <%s>\n",Cmd
  78.     while ((ret = (Cmd | getline)) == 1) {
  79.         if ($1 == "")
  80.         pid = $3
  81.         else
  82.         pid = $2
  83.         if (pid in Proc2TTYs) {
  84.         print psLines[pid] = sprintf("%3s %s",Proc2TTYs[pid],$0)
  85.         ProcOrder[++POInd] = pid
  86.         }
  87.     }
  88.     if (ret == -1) {
  89.         printf "Command '%s' failed.  Exiting.\n",Cmd > "/dev/stderr"
  90.         exit 1
  91.     }
  92.     close(Cmd)
  93.     }
  94.     if (Interactive)
  95.     ProcList = AskProcs(POInd,ProcOrder,psLines)
  96.     if (Kill && ProcList != "")
  97.     system("kill -9 " ProcList)
  98. }
  99.  
  100. # ProcOrder[] contains process IDs, indexed 1..NumProcs.
  101. # psLines[] contains ps lines, index by pid.
  102. # The fate of each process is queried for.
  103. # A list of the pids to be killed is returned.
  104. function AskProcs(NumProcs,ProcOrder,psLines,  Kill,ProcList,i,pid,All) {
  105.     Kill = 1
  106.     All = 0
  107.     ProcList = ""
  108.     print \
  109. "Type y to kill proc, n or return to skip it, Y or N to kill or skip the rest:"
  110.     for (i = 1; i <= NumProcs; i++) {
  111.     pid = ProcOrder[i]
  112.     if (All)
  113.         ProcList = ProcList " " pid
  114.     else {
  115.         printf "%s: ",substr(psLines[pid],1,75)
  116.         if ((getline < "/dev/tty") == -1) {
  117.         printf "Read from /dev/tty failed.  Exiting.\n",Cmd \
  118.         > "/dev/stderr"
  119.         exit 1
  120.         }
  121.         if ($0 ~ /^y/)
  122.         ProcList = ProcList " " pid
  123.         else if ($0 ~ /^Y/) {
  124.         ProcList = ProcList " " pid
  125.         All = 1
  126.         }
  127.         else if ($0 ~ /^N/)
  128.         break
  129.     }
  130.     }
  131.     if (ProcList != "")
  132.     print "Killing:" ProcList
  133.     return ProcList
  134. }
  135.  
  136. function Proc_fuserLine(Progress) {
  137.     # Only pay attention to ptys with processes
  138.     if ($1 ~ /^[pt]typ/) {
  139.     ttynum = substr($1,5)+0 
  140.     if (NF > 1 && $2 ~ /[0-9]/)
  141.         return ttynum
  142.     else if (Progress && $1 ~ "^p")
  143.         printf "-%s",ttynum
  144.     }
  145.     return ""
  146. }
  147.  
  148. function Proc_lsofLine() {
  149.     # lsof output might or might not have leading /dev/
  150.     sub("^/dev/","",$NF)
  151.     if ($NF ~ "^[pt]typ")
  152.     return substr($NF,5)+0 
  153.     else {
  154.     if (Debug)
  155.         print "^^^ Non-file line returned by lsof"
  156.     return ""
  157.     }
  158. }
  159.  
  160. function FindttyProcs(ttyList,Use_lsof,Progress,
  161. pty,S,Cmd,ret,NumFound) {
  162.     
  163.     for (pty in ttyList)
  164.     S = S " ptyp" pty
  165.     Cmd = "cd /dev; exec " (Use_lsof ? "lsof -HOP" : "fuser") " " S " 2>&1"
  166.     if (Debug)
  167.     print ":" Cmd ":"
  168.     # Format returned by fuser:
  169.     #    ptyp0:    29592
  170.     # FS includes : so it will not be in $1
  171.     # Format returned by lsof:
  172. # telnetd    5056        0    3u   CHR    59,   0     0x13eb    706 /dev/ptyp0
  173.  
  174.     while ((ret = (Cmd | getline)) == 1) {
  175.     if (Debug)
  176.         print "\n" $0
  177.     if (Use_lsof)
  178.         ttynum = Proc_lsofLine()
  179.     else
  180.         ttynum = Proc_fuserLine(Progress)
  181.     if (Debug)
  182.         print "ttynum: " ttynum
  183.     if (ttynum != "" && ttynum in ttyList) {
  184.         # Remove slave of this pty from report; its master is open.
  185.         delete ttyList[ttynum]
  186.         NumFound++
  187.         if (Progress)
  188.         printf "+%s",ttynum
  189.     }
  190.     }
  191.     if (ret == -1) {
  192.     printf "Command '%s' failed.  Exiting.\n",Cmd > "/dev/stderr"
  193.     exit 1
  194.     }
  195.     close(Cmd)
  196.     if (Progress)
  197.     print ""
  198.     return NumFound
  199. }
  200.  
  201. # For each slave pty in use whose master side is not open, adds the tty to
  202. # Proc2TTYs.
  203. # Proc2TTYs[] is indexed by PID; the value is a comma separated list of ptys
  204. # numbers that that the process with that PID has open.
  205. # The number of the highest such tty is returned, or -1 if there are none.
  206.  
  207. # fuser is very slow, so crash is used to find only those ttyps that are open;
  208. # fuser is run on their masters, and then on only those whose master is not
  209. # open (to get proc list).  crash knows process group, but that isn't good
  210. # enough.
  211. function ChkPtys(Progress,Proc2TTYs,  
  212. Cmd,ttynum,maxpty,OpenPtys,pty,S,NumOpen,ret,Use_lsof)
  213. {
  214.     NumOpen = FindOpenPtySlaves(OpenPtys)
  215.     if (!Quiet)
  216.     printf "%d pty slave(s) open.\n",NumOpen
  217.     if (NumOpen == 0)
  218.     return -1
  219.     Cmd = "type lsof"
  220.     Cmd | getline
  221.     close(Cmd)
  222.     Use_lsof = ($2 == "is")
  223.     if (Progress)
  224.     printf "Checking for open pty masters:"
  225.     # Remove all ptys whose master is open from OpenPtys[]
  226.     NumOpen -= FindttyProcs(OpenPtys,Use_lsof,Progress)
  227.     if (!Quiet)
  228.     printf "%d open pty slave(s) whose master is not open.\n",NumOpen
  229.     if (NumOpen == 0)
  230.     return -1
  231.  
  232.     if (Progress)
  233.     printf "Finding processes on pty slaves:"
  234.  
  235.     #### This code is to be replaced
  236.     maxpty = -1
  237.     # Any ttys remaining in OpenPtys are those whose master side is not open
  238.     S = ""
  239.     for (pty in OpenPtys)
  240.     S = S " ttyp" pty
  241.     Cmd = "cd /dev; exec fuser " S " 2>&1"
  242.     if (Debug)
  243.     print ":" Cmd ":"
  244.     while ((ret = (Cmd | getline)) == 1) {
  245.     if (Debug)
  246.         print "\n" $0
  247.     # Only pay attention to ttys with processes
  248.     if ($1 ~ /^ttyp/) {
  249.         ttynum = substr($1,5)+0 
  250.         if (NF > 1 && $2 ~ /[0-9]/) {
  251.         if (ttynum > maxpty)
  252.             maxpty = ttynum
  253.         $1 = ""        # delete tty name
  254.         NotOpen("ttyp" ttynum,$0,Proc2TTYs)
  255.         if (Progress)
  256.             printf "+%s",ttynum
  257.         }
  258.         else {
  259.         # fuser found no processes for this slave even though crash
  260.         # reported it open.
  261.         if (Debug)
  262.             printf \
  263.         "\ncrash reports %s open but fuser finds no processes for it.\n",$1
  264.         if (Progress)
  265.             printf "-%s",ttynum
  266.         }
  267.     }
  268.     }
  269.     if (ret == -1) {
  270.     printf "Command '%s' failed.  Exiting.\n",Cmd > "/dev/stderr"
  271.     exit 1
  272.     }
  273.     close(Cmd)
  274.     if (Progress)
  275.     print ""
  276.     if (Debug)
  277.     printf "maxpty: %d",maxpty
  278.     #### End of code to be replaced
  279.  
  280.     return maxpty
  281. }
  282.  
  283. # Adds tty to the list of ttys that each process has open (Proc2TTYs[])
  284. function NotOpen(tty,ProcList,Proc2TTYs,  TTYProcs,pid) {
  285.     MakeSet(TTYProcs,ProcList," +")
  286.     for (pid in TTYProcs)
  287.     if (pid in Proc2TTYs)
  288.         Proc2TTYs[pid] = Proc2TTYs[pid] "," tty
  289.     else
  290.         Proc2TTYs[pid] = tty
  291. }
  292.  
  293. # Example crash session:
  294. #> tty -t spt
  295. #spt TABLE SIZE = 80
  296. #SLOT OUT RAW CAN DEL  PGRP STATE
  297. #   0   0   0   0   0 28898 isop carr islp
  298. #   1   0   0   0   0 24082 wtop isop
  299. #   2   0   0   0   0     0 isop
  300. #   3   0   0   0   0   930 isop carr islp ttxn
  301. #   4   0   0   0   0 28459 isop carr islp ttxn
  302. #   5   0   0   0   0   925 isop carr islp ttxn
  303. #   6   0   0   0   0   934 isop carr islp
  304. #   7   0   0   0   0 28965 isop carr islp ttxn
  305. #   8   0   0   0   0 29228 isop carr islp
  306. #   9   0   0   0   0 18200 isop carr
  307. #  10 220   0   0   0 29209 isop carr
  308. #  11   0   0   0   0 28239 isop carr
  309. #  12   1   0   0   0 28125 isop carr
  310. #  13   0   0   0   0 29493 isop carr
  311. #  14   0   0   0   0  4265 isop carr islp
  312. #  16   0   0   0   0 24807 isop carr
  313. #  17   0   2   0   1 25988 isop carr
  314. #  18   0   0   0   0 19728 wtop isop
  315. #  19   0   0   0   0 28048 isop carr
  316. #  20   0   0   0   0 22115 isop carr islp
  317. #  21   0   0   0   0 27688 isop carr islp rto
  318. #  22   0   0   0   0 22512 isop carr
  319. #  23   0   0   0   0     0 isop
  320. #  24   0   0   0   0 23127 isop carr
  321. #  25   0   0   0   0 28679 isop carr
  322. #  26   0   0   0   0 29528 isop carr
  323. #  27   0   0   0   0 24240 isop carr
  324. #  28   0   0   0   0 20162 isop carr islp
  325. #  30   0   0   0   0 28843 isop carr
  326. #  31   0   0   0   0 29256 isop carr
  327. #  32   0   0   0   0   605 isop carr
  328. # Puts a numeric index for each open pty in OpenPtys.
  329. # Returns number of pty slaves in use.
  330. function FindOpenPtySlaves(OpenPtys,  Cmd,Count,ret) {
  331.     Cmd = "echo tty -t spt | /etc/crash"
  332.     Count = 0    # Make sure a numeric value will be returned if none open
  333.     while ((Cmd | getline) == 1) {
  334.     if (Debug)
  335.         print $0
  336.     # tty num is $2 because FS has been set
  337.     if ($2 ~ /[0-9]/) {
  338.         OpenPtys[$2]
  339.         Count++
  340.     }
  341.     }
  342.     if (ret == -1) {
  343.     printf "Command '%s' failed.  Exiting.\n",Cmd > "/dev/stderr"
  344.     exit 1
  345.     }
  346.     close(Cmd)
  347.     return Count
  348. }
  349.  
  350. # MakeSet: make a set from a list.
  351. # An index with the name of each element of the list
  352. # is created in the given array.
  353. # Input variables: 
  354. # Elements is a string containing the list of elements.
  355. # Sep is the character that separates the elements of the list.
  356. # Output variables:
  357. # Set is the array.
  358. # Return value: the number of elements added to the set.
  359. function MakeSet(Set,Elements,Sep,  Num,Names) {
  360.     Num = split(Elements,Names,Sep)
  361.     for (; Num; Num--)
  362.     Set[Names[Num]]
  363.     return Num
  364. }
  365.  
  366. # Returns 1 if Set is empty, 0 if not.
  367. function IsEmpty(Set,  i) {
  368.     for (i in Set)
  369.     return 0
  370.     return 1
  371. }
  372.  
  373. function CopySet(From,To,  Elem) {
  374.     for (Elem in From)
  375.     To[Elem]
  376. }
  377.  
  378. ### Start of ProcArgs library
  379. # @(#) ProcArgs 1.11 96/12/08
  380. # 92/02/29 john h. dubois iii (john@armory.com)
  381. # 93/07/18 Added "#" arg type
  382. # 93/09/26 Do not count -h against MinArgs
  383. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  384. #          Removed meaning of "+" or "-" by itself.
  385. # 94/03/08 Added & option and *()< option types.
  386. # 94/04/02 Added NoRCopt to Opts()
  387. # 94/06/11 Mark numeric variables as such.
  388. # 94/07/08 Opts(): Do not require any args if h option is given.
  389. # 95/01/22 Record options given more than once.  Record option num in argv.
  390. # 95/06/08 Added ExclusiveOptions().
  391. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  392. #          Expand $VARNAME at the start of its filenames.
  393. #          Let varname=0 and -option- turn off an option.
  394. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  395. #          of the vars should be searched for in the environment.
  396. #          Check for duplicate rcfiles.
  397. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  398. #          now return various negatives values on error, not just -1, and
  399. #          Opts() may set Err to various positive values, not just 1.
  400. #          Added AllowUnrecOpt.
  401. # 96/05/23 Check type given for & option
  402. # 96/06/15 Re-port to awk
  403. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  404. #          used by other functions.
  405. # 96/10/15 Added OptChars
  406. # 96/11/01 Added exOpts arg to Opts()
  407. # 96/11/16 Added ; type
  408. # 96/12/08 Added Opt2Set() & Opt2Sets()
  409. # 96/12/27 Added CmdLineOpt()
  410.  
  411. # optlist is a string which contains all of the possible command line options.
  412. # A character followed by certain characters indicates that the option takes
  413. # an argument, with type as follows:
  414. # :    String argument
  415. # ;    Non-empty string argument
  416. # *    Floating point argument
  417. # (    Non-negative floating point argument
  418. # )    Positive floating point argument
  419. # #    Integer argument
  420. # <    Non-negative integer argument
  421. # >    Positive integer argument
  422. # The only difference the type of argument makes is in the runtime argument
  423. # error checking that is done.
  424.  
  425. # The & option is a special case used to get numeric options without the
  426. # user having to give an option character.  It is shorthand for [-+.0-9].
  427. # If & is included in optlist and an option string that begins with one of
  428. # these characters is seen, the value given to "&" will include the first
  429. # char of the option.  & must be followed by a type character other than ":"
  430. # or ";".
  431. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  432.  
  433. # Strings in argv[] which begin with "-" or "+" are taken to be
  434. # strings of options, except that a string which consists solely of "-"
  435. # or "+" is taken to be a non-option string; like other non-option strings,
  436. # it stops the scanning of argv and is left in argv[].
  437. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  438. # If an option takes an argument, the argument may either immediately
  439. # follow it or be given separately.
  440. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  441. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  442. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  443. # this feature had a flaw that caused problems in some cases.  See the OptChars
  444. # parameter to explicitly set the option-specifier characters.
  445.  
  446. # If an option that does not take an argument is given,
  447. # an index with its name is created in Options and its value is set to the
  448. # number of times it occurs in argv[].
  449.  
  450. # If an option that does take an argument is given, an index with its name is
  451. # created in Options and its value is set to the value of the argument given
  452. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  453. # If an option that takes an argument is given more than once,
  454. # Options[option-name,"count"] is incremented, and the value is assigned to
  455. # the index (option-name,instance) where instance is 2 for the second occurance
  456. # of the option, etc.
  457. # In other words, the first time an option with a value is encountered, the
  458. # value is assigned to an index consisting only of its name; for any further
  459. # occurances of the option, the value index has an extra (count) dimension.
  460.  
  461. # The sequence number for each option found in argv[] is stored in
  462. # Options[option-name,"num",instance], where instance is 1 for the first
  463. # occurance of the option, etc.  The sequence number starts at 1 and is
  464. # incremented for each option, both those that have a value and those that
  465. # do not.  Options set from a config file have a value of 0 assigned to this.
  466.  
  467. # Options and their arguments are deleted from argv.
  468. # Note that this means that there may be gaps left in the indices of argv[].
  469. # If compress is nonzero, argv[] is packed by moving its elements so that
  470. # they have contiguous integer indices starting with 0.
  471. # Option processing will stop with the first unrecognized option, just as
  472. # though -- was given except that unlike -- the unrecognized option will not be
  473. # removed from ARGV[].  Normally, an error value is returned in this case.
  474. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  475. # be found, so the number of remaining arguments is returned instead.
  476. # If OptChars is not a null string, it is the set of characters that indicate
  477. # that an argument is an option string if the string begins with one of the
  478. # characters.  A string consisting solely of two of the same option-indicator
  479. # characters stops the scanning of argv[].  The default is "-+".
  480. # argv[0] is not examined.
  481. # The number of arguments left in argc is returned.
  482. # If an error occurs, the global string OptErr is set to an error message
  483. # and a negative value is returned.
  484. # Current error values:
  485. # -1: option that required an argument did not get it.
  486. # -2: argument of incorrect type supplied for an option.
  487. # -3: unrecognized (invalid) option.
  488. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  489. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  490. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  491. {
  492. # ArgNum is the index of the argument being processed.
  493. # ArgsLeft is the number of arguments left in argv.
  494. # Arg is the argument being processed.
  495. # ArgLen is the length of the argument being processed.
  496. # ArgInd is the position of the character in Arg being processed.
  497. # Option is the character in Arg being processed.
  498. # Pos is the position in OptList of the option being processed.
  499. # NumOpt is true if a numeric option may be given.
  500.     ArgsLeft = argc
  501.     NumOpt = index(OptList,"&")
  502.     OptionNum = 0
  503.     if (OptChars == "")
  504.     OptChars = "-+"
  505.     while (OptChars != "") {
  506.     c = substr(OptChars,1,1)
  507.     OptChars = substr(OptChars,2)
  508.     OptCharSet[c]
  509.     OptTerm[c c]
  510.     }
  511.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  512.     Arg = argv[ArgNum]
  513.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  514.         break    # Not an option; quit
  515.     if (Arg in OptTerm) {
  516.         delete argv[ArgNum]
  517.         ArgsLeft--
  518.         break
  519.     }
  520.     ArgLen = length(Arg)
  521.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  522.         Option = substr(Arg,ArgInd,1)
  523.         if (NumOpt && Option ~ /[-+.0-9]/) {
  524.         # If this option is a numeric option, make its flag be & and
  525.         # its option string flag position be the position of & in
  526.         # the option string.
  527.         Option = "&"
  528.         Pos = NumOpt
  529.         # Prefix Arg with a char so that ArgInd will point to the
  530.         # first char of the numeric option.
  531.         Arg = "&" Arg
  532.         ArgLen++
  533.         }
  534.         # Find position of flag in option string, to get its type (if any).
  535.         # Disallow & as literal flag.
  536.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  537.         if (AllowUnrecOpt) {
  538.             Escape = 1
  539.             break
  540.         }
  541.         else {
  542.             OptErr = "Invalid option: " specGiven Option
  543.             return -3
  544.         }
  545.         }
  546.  
  547.         # Find what the value of the option will be if it takes one.
  548.         # NeedNextOpt is true if the option specifier is the last char of
  549.         # this arg, which means that if the option requires a value it is
  550.         # the next arg.
  551.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  552.         if (GotValue = ArgNum + 1 < argc)
  553.             Value = argv[ArgNum+1]
  554.         }
  555.         else {    # Value is included with option
  556.         Value = substr(Arg,ArgInd + 1)
  557.         GotValue = 1
  558.         }
  559.  
  560.         if (HadValue = AssignVal(Option,Value,Options,
  561.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  562.         specGiven)) {
  563.         if (HadValue < 0)    # error occured
  564.             return HadValue
  565.         if (HadValue == 2)
  566.             ArgInd++    # Account for the single-char value we used.
  567.         else {
  568.             if (NeedNextOpt) {    # option took next arg as value
  569.             delete argv[++ArgNum]
  570.             ArgsLeft--
  571.             }
  572.             break    # This option has been used up
  573.         }
  574.         }
  575.     }
  576.     if (Escape)
  577.         break
  578.     # Do not delete arg until after processing of it, so that if it is not
  579.     # recognized it can be left in ARGV[].
  580.     delete argv[ArgNum]
  581.     ArgsLeft--
  582.     }
  583.     if (compress != 0) {
  584.     dest = 1
  585.     src = argc - ArgsLeft + 1
  586.     for (count = ArgsLeft - 1; count; count--) {
  587.         ARGV[dest] = ARGV[src]
  588.         dest++
  589.         src++
  590.     }
  591.     }
  592.     return ArgsLeft
  593. }
  594.  
  595. # Assignment to values in Options[] occurs only in this function.
  596. # Option: Option specifier character.
  597. # Value: Value to be assigned to option, if it takes a value.
  598. # Options[]: Options array to return values in.
  599. # ArgType: Argument type specifier character.
  600. # GotValue: Whether any value is available to be assigned to this option.
  601. # Name: Name of option being processed.
  602. # OptionNum: Number of this option (starting with 1) if set in argv[],
  603. #     or 0 if it was given in a config file or in the environment.
  604. # SingleOpt: true if the value (if any) that is available for this option was
  605. #     given as part of the same command line arg as the option.  Used only for
  606. #     options from the command line.
  607. # specGiven is the option specifier character use, if any (e.g. - or +),
  608. # for use in error messages.
  609. # Global variables: OptErr
  610. # Return value: negative value on error, 0 if option did not require an
  611. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  612. # the arg.
  613. # Current error values:
  614. # -1: Option that required an argument did not get it.
  615. # -2: Value of incorrect type supplied for option.
  616. # -3: Bad type given for option &
  617. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  618. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  619.     # If option takes a value...    [
  620.     NumTypes = "*()#<>]"
  621.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  622.     OptErr = "Bad type given for & option"
  623.     return -3
  624.     }
  625.  
  626.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  627.     if (!GotValue) {
  628.         if (Name != "")
  629.         OptErr = "Variable requires a value -- " Name
  630.         else
  631.         OptErr = "option requires an argument -- " Option
  632.         return -1
  633.     }
  634.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  635.         OptErr = Err
  636.         return -2
  637.     }
  638.     # Mark this as a numeric variable; will be propogated to Options[] val.
  639.     if (ArgType != ":" && ArgType != ";")
  640.         Value += 0
  641.     if ((Instance = ++Options[Option,"count"]) > 1)
  642.         Options[Option,Instance] = Value
  643.     else
  644.         Options[Option] = Value
  645.     }
  646.     # If this is an environ or rcfile assignment & it was given a value...
  647.     else if (!OptionNum && Value != "") {
  648.     UsedValue = 1
  649.     # If the value is "0" or "-" and this is the first instance of it,
  650.     # do not set Options[Option]; this allows an assignment in an rcfile to
  651.     # turn off an option (for the simple "Option in Options" test) in such
  652.     # a way that it cannot be turned on in a later file.
  653.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  654.         Instance = 1
  655.     else
  656.         Instance = ++Options[Option]
  657.     # Save the value even though this is a flag
  658.     Options[Option,Instance] = Value
  659.     }
  660.     # If this is a command line flag and has a - following it in the same arg,
  661.     # it is being turned off.
  662.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  663.     UsedValue = 2
  664.     if (Option in Options)
  665.         Instance = ++Options[Option]
  666.     else
  667.         Instance = 1
  668.     Options[Option,Instance]
  669.     }
  670.     # If this is a flag assignment without a value, increment the count for the
  671.     # flag unless it was turned off.  The indicator for a flag being turned off
  672.     # is that the flag index has not been set in Options[] but it has an
  673.     # instance count.
  674.     else if (Option in Options || !((Option,1) in Options))
  675.     # Increment number of times this flag seen; will inc null value to 1
  676.     Instance = ++Options[Option]
  677.     Options[Option,"num",Instance] = OptionNum
  678.     return UsedValue
  679. }
  680.  
  681. # Option is the option letter
  682. # Value is the value being assigned
  683. # Name is the var name of the option, if any
  684. # ArgType is one of:
  685. # :    String argument
  686. # ;    Non-null string argument
  687. # *    Floating point argument
  688. # (    Non-negative floating point argument
  689. # )    Positive floating point argument
  690. # #    Integer argument
  691. # <    Non-negative integer argument
  692. # >    Positive integer argument
  693. # specGiven is the option specifier character use, if any (e.g. - or +),
  694. # for use in error messages.
  695. # Returns null on success, err string on error
  696. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  697.     if (ArgType == ":")
  698.     return ""
  699.     if (ArgType == ";") {
  700.     if (Value == "")
  701.         Err = "must be a non-empty string"
  702.     }
  703.     # A number begins with optional + or -, and is followed by a string of
  704.     # digits or a decimal with digits before it, after it, or both
  705.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  706.     Err = "must be a number"
  707.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  708.     Err = "may not include a fraction"
  709.     else if (ArgType ~ "[()<>]" && Value < 0)
  710.     Err = "may not be negative"
  711.     # (
  712.     else if (ArgType ~ "[)>]" && Value == 0)
  713.     Err = "must be a positive number"
  714.     if (Err != "") {
  715.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  716.     if (Name != "")
  717.         return ErrStr "variable " substr(Name,1,1) " " Err
  718.     else {
  719.         if (Option == "&")
  720.         Option = Value
  721.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  722.     }
  723.     }
  724.     else
  725.     return ""
  726. }
  727.  
  728. # Note: only the above functions are needed by ProcArgs.
  729. # The rest of these functions call ProcArgs() and also do other
  730. # option-processing stuff.
  731.  
  732. # Opts: Process command line arguments.
  733. # Opts processes command line arguments using ProcArgs()
  734. # and checks for errors.  If an error occurs, a message is printed
  735. # and the program is exited.
  736. #
  737. # Input variables:
  738. # Name is the name of the program, for error messages.
  739. # Usage is a usage message, for error messages.
  740. # OptList the option description string, as used by ProcArgs().
  741. # MinArgs is the minimum number of non-option arguments that this
  742. # program should have, non including ARGV[0] and +h.
  743. # If the program does not require any non-option arguments,
  744. # MinArgs should be omitted or given as 0.
  745. # rcFiles, if given, is a colon-seprated list of filenames to read for
  746. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  747. # by the value of the environment variable HOME.  If a filename begins with
  748. # $, the part from the character after the $ up until (but not including)
  749. # the first character not in [a-zA-Z0-9_] will be searched for in the
  750. # environment; if found its value will be substituted, if not the filename will
  751. # be discarded.
  752. # rcfiles are read in the order given.
  753. # Values given in them will not override values given on the command line,
  754. # and values given in later files will not override those set in earlier
  755. # files, because AssignVal() will store each with a different instance index.
  756. # The first instance of each variable, either on the command line or in an
  757. # rcfile, will be stored with no instance index, and this is the value
  758. # normally used by programs that call this function.
  759. # VarNames is a comma-separated list of variable names to map to options,
  760. # in the same order as the options are given in OptList.
  761. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  762. # searched for in the environment.  If set to -1, all values will be searched
  763. # for in the environment.  Values given in the environment will override
  764. # those given in the rcfiles but not those given on the command line.
  765. # NoRCopt, if given, is an additional letter option that if given on the
  766. # command line prevents the rcfiles from being read.
  767. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  768. # ExclusiveOptions() for a description of exOpts.
  769. # Special options:
  770. # If x is made an option and is given, some debugging info is output.
  771. # h is assumed to be the help option.
  772.  
  773. # Global variables:
  774. # The command line arguments are taken from ARGV[].
  775. # The arguments that are option specifiers and values are removed from
  776. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  777. # The number of elements in ARGV[] should be in ARGC.
  778. # After processing, ARGC is set to the number of elements left in ARGV[].
  779. # The option values are put in Options[].
  780. # On error, Err is set to a positive integer value so it can be checked for in
  781. # an END block.
  782. # Return value: The number of elements left in ARGV is returned.
  783. # Must keep OptErr global since it may be set by InitOpts().
  784. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  785. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  786.     if (MinArgs == "")
  787.     MinArgs = 0
  788.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  789.     optChars)
  790.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  791.     if (ArgsLeft >= 0) {
  792.         OptErr = "Not enough arguments"
  793.         Err = 4
  794.     }
  795.     else
  796.         Err = -ArgsLeft
  797.     printf "%s: %s.\nUse -h for help.\n%s\n",
  798.     Name,OptErr,Usage > "/dev/stderr"
  799.     exit 1
  800.     }
  801.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  802.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  803.     {
  804.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  805.     Err = -e
  806.     exit 1
  807.     }
  808.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  809.     {
  810.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  811.     Err = 1
  812.     exit 1
  813.     }
  814.     return ArgsLeft
  815. }
  816.  
  817. # ReadConfFile(): Read a file containing var/value assignments, in the form
  818. # <variable-name><assignment-char><value>.
  819. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  820. # line and whitespace between the variable name and the assignment character) 
  821. # is stripped.  Lines that do not contain an assignment operator or which
  822. # contain a null variable name are ignored, other than possibly being noted in
  823. # the return value.  If more than one assignment is made to a variable, the
  824. # first assignment is used.
  825. # Input variables:
  826. # File is the file to read.
  827. # Comment is the line-comment character.  If it is found as the first non-
  828. #     whitespace character on a line, the line is ignored.
  829. # Assign is the assignment string.  The first instance of Assign on a line
  830. #     separates the variable name from its value.
  831. # If StripWhite is true, whitespace around the value (whitespace between the
  832. #     assignment char and trailing whitespace on the line) is stripped.
  833. # VarPat is a pattern that variable names must match.  
  834. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  835. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  836. #     a line; no assignment operator is needed.  These variables are set in
  837. #     the output array with a null value.  Lines containing nothing but
  838. #     whitespace are still ignored.
  839. # Output variables:
  840. # Values[] contains the assignments, with the indexes being the variable names
  841. #     and the values being the assigned values.
  842. # Lines[] contains the line number that each variable occured on.  A flag set
  843. #     is record by giving it an index in Lines[] but not in Values[].
  844. # Return value:
  845. # If any errors occur, a string consisting of descriptions of the errors
  846. # separated by newlines is returned.  In no case will the string start with a
  847. # numeric value.  If no errors occur,  the number of lines read is returned.
  848. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  849. FlagsOK,
  850. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  851.     if (Comment != "")
  852.     Comment = "^" Comment
  853.     AssignLen = length(Assign)
  854.     if (VarPat == "")
  855.     VarPat = "."    # null varname not allowed
  856.     while ((Status = (getline Line < File)) == 1) {
  857.     LineNum++
  858.     sub("^[ \t]+","",Line)
  859.     if (Line == "")        # blank line
  860.         continue
  861.     if (Comment != "" && Line ~ Comment)
  862.         continue
  863.     if (Pos = index(Line,Assign)) {
  864.         Var = substr(Line,1,Pos-1)
  865.         Val = substr(Line,Pos+AssignLen)
  866.         if (StripWhite) {
  867.         sub("^[ \t]+","",Val)
  868.         sub("[ \t]+$","",Val)
  869.         }
  870.     }
  871.     else {
  872.         Var = Line    # If no value, var is entire line
  873.         Val = ""
  874.     }
  875.     if (!FlagsOK && Val == "") {
  876.         Errs = Errs \
  877.         sprintf("\nBad assignment on line %d of file %s: %s",
  878.         LineNum,File,Line)
  879.         continue
  880.     }
  881.     sub("[ \t]+$","",Var)
  882.     if (Var !~ VarPat) {
  883.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  884.         LineNum,File,Var)
  885.         continue
  886.     }
  887.     if (!(Var in Lines)) {
  888.         Lines[Var] = LineNum
  889.         if (Pos)
  890.         Values[Var] = Val
  891.     }
  892.     }
  893.     if (Status)
  894.     Errs = Errs "\nCould not read file " File
  895.     close(File)
  896.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  897. }
  898.  
  899. # Variables:
  900. # Data is stored in Options[].
  901. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  902. # Global vars:
  903. # Sets OptErr.  Uses ENVIRON[].
  904. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  905. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  906. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  907. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  908.     split("",filesRead,"")    # make awk know this is an array
  909.     NumVars = split(VarNames,Vars,",")
  910.     TypesInd = Ret = 0
  911.     if (EnvSearch == -1)
  912.     EnvSearch = NumVars
  913.     for (i = 1; i <= NumVars; i++) {
  914.     Var = Vars[i]
  915.     CharOpt = substr(OptList,++TypesInd,1)
  916.     if (CharOpt ~ "^[:;*()#<>&]$")
  917.         CharOpt = substr(OptList,++TypesInd,1)
  918.     Map[Var] = CharOpt
  919.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  920.     # Do not overwrite entries from environment
  921.     if (i <= EnvSearch && Var in ENVIRON &&
  922.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  923.         return Err
  924.     }
  925.  
  926.     numrcFiles = split(rcFiles,fNames,":")
  927.     for (i = 1; i <= numrcFiles; i++) {
  928.     rcFile = fNames[i]
  929.     if (rcFile ~ "^~/")
  930.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  931.     else if (rcFile ~ /^\$/) {
  932.         rcFile = substr(rcFile,2)
  933.         match(rcFile,"^[a-zA-Z0-9_]*")
  934.         envvar = substr(rcFile,1,RLENGTH)
  935.         if (envvar in ENVIRON)
  936.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  937.         else
  938.         continue
  939.     }
  940.     if (rcFile in filesRead)
  941.         continue
  942.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  943.     # may be the same
  944.     filesRead[rcFile]
  945.     if ("x" in Options)
  946.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  947.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  948.     if (retStr > 0)
  949.         READ_RCFILE = 1
  950.     else if (ret != "") {
  951.         OptErr = retStr
  952.         Ret = -1
  953.     }
  954.     for (Var in Lines)
  955.         if (Var in Map) {
  956.         if ((Err = AssignVal(Map[Var],
  957.         Var in Values ? Values[Var] : "",Options,Types[Var],
  958.         Var in Values,Var,0)) < 0)
  959.             return Err
  960.         }
  961.         else {
  962.         OptErr = sprintf(\
  963.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  964.         Lines[Var],rcFile)
  965.         Ret = -1
  966.         }
  967.     }
  968.  
  969.     if ("x" in Options)
  970.     for (Var in Map)
  971.         if (Map[Var] in Options)
  972.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  973.         "/dev/stderr"
  974.         else
  975.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  976.     return Ret
  977. }
  978.  
  979. # OptSets is a semicolon-separated list of sets of option sets.
  980. # Within a list of option sets, the option sets are separated by commas.  For
  981. # each set of sets, if any option in one of the sets is in Options[] AND any
  982. # option in one of the other sets is in Options[], an error string is returned.
  983. # If no conflicts are found, nothing is returned.
  984. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  985. # the exclusions presented by the first set of sets (ab,def,g) if:
  986. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  987. # (a or b is in Options[]) AND (g is in Options) OR
  988. # (d, e, or f is in Options[]) AND (g is in Options)
  989. # An error will be returned due to the exclusions presented by the second set
  990. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  991. # todo: make options given on command line unset options given in config file
  992. # todo: that they conflict with.
  993. function ExclusiveOptions(OptSets,Options,
  994. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  995. SetNum,OSetNum) {
  996.     NumSetSets = split(OptSets,SetSets,";")
  997.     # For each set of sets...
  998.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  999.     # NumSets is the number of sets in this set of sets.
  1000.     NumSets = split(SetSets[SetSet],Sets,",")
  1001.     # For each set in a set of sets except the last...
  1002.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1003.         s1 = Sets[SetNum]
  1004.         L1 = length(s1)
  1005.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1006.         # If any of the options in this set was given, check whether
  1007.         # any of the options in the other sets was given.  Only check
  1008.         # later sets since earlier sets will have already been checked
  1009.         # against this set.
  1010.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1011.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1012.             s2 = Sets[OSetNum]
  1013.             L2 = length(s2)
  1014.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1015.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1016.                 ErrStr = ErrStr "\n"\
  1017.                 sprintf("Cannot give both %s and %s options.",
  1018.                 c1,c2)
  1019.             }
  1020.     }
  1021.     }
  1022.     if (ErrStr != "")
  1023.     return substr(ErrStr,2)
  1024.     return ""
  1025. }
  1026.  
  1027. # The value of each instance of option Opt that occurs in Options[] is made an
  1028. # index of Set[].
  1029. # The return value is the number of instances of Opt in Options.
  1030. function Opt2Set(Options,Opt,Set,  count) {
  1031.     if (!(Opt in Options))
  1032.     return 0
  1033.     Set[Options[Opt]]
  1034.     count = Options[Opt,"count"]
  1035.     for (; count > 1; count--)
  1036.     Set[Options[Opt,count]]
  1037.     return count
  1038. }
  1039.  
  1040. # The value of each instance of option Opt that occurs in Options[] that
  1041. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1042. # Other values are made indexes of Set[].
  1043. # The return value is the number of instances of Opt in Options.
  1044. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1045.     ret = Opt2Set(Options,Opt,aSet)
  1046.     for (value in aSet)
  1047.     if (substr(value,1,1) == "!")
  1048.         nSet[substr(value,2)]
  1049.     else
  1050.         Set[value]
  1051.     return ret
  1052. }
  1053.  
  1054. # Returns true if option Opt was given on the command line.
  1055. function CmdLineOpt(Options,Opt,  i) {
  1056.     for (i = 1; (Opt,"num",i) in Options; i++)
  1057.     if (Options[Opt,"num",i] != 0)
  1058.         return 1
  1059.     return 0
  1060. }
  1061. ### End of ProcArgs library
  1062.